package org.tabularium.text.nlp;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;

import edu.illinois.cs.cogcomp.core.datastructures.IntPair;
import edu.illinois.cs.cogcomp.core.datastructures.Pair;
import edu.illinois.cs.cogcomp.core.math.ArgMax;
import edu.illinois.cs.cogcomp.edison.annotators.Tokenizer.LBJTokenizer;
import edu.illinois.cs.cogcomp.edison.sentences.TextAnnotation;
import edu.illinois.cs.cogcomp.mrcs.entailment.LlmEntailmentClassifier;
import edu.illinois.cs.cogcomp.ms.util.StringUtil;


/**
 * LlmComparator is an asymmetric text span metric (sentences, paragraphs, documents...)
 * that takes two text spans and compares them token-by-token. The score is a normalized
 * measure (i.e. is in the interval [0, 1]) of the sum of similarities of tokens in the
 * second text span to tokens in the first text span.  
 * 
 * Optionally, LlmComparator uses a word similarity metric to compare tokens, and a list
 * of stopwords to filter non-content words.
 * 
 * @author mssammon
 */

public class LlmComparator {
	
	protected HashSet< String > m_stopwords;
//	protected String m_stopwordFile = 
//		"llmStopwords.txt";

	protected boolean m_useStopwords;
	protected boolean m_useLemmas;
	protected boolean m_useMetric;
	protected boolean m_computeSimpleScore;
	protected double m_simpleScoreThreshold;
	protected String m_metricName;
	protected String m_metricHost;
	protected int m_metricPort;
	
	public static final String[] DEFAULT_STOPWORDS = {
		"a", "accordingly", "actually", "also", "although", "always", "am", "an", "and", "another", "any", "anyhow", "anyone", "anything", "anyway", "anyways", "anywhere", "are", "as", "at", "be", "besides", "but", "by", "can", "certain", "certainly", "clearly", "COMMA", "currently", "definitely", "etc", "for", "from", "furthermore", "he", "here", "him", "himself", "his", "how", "if", "in", "inasmuch", "into", "is", "it", "its", "itself", "just", "many", "may", "maybe", "me", "meanwhile", "merely", "my", "myself", "no", "nor", "not", "now", "obviously", "of", "off", "often", "on", "once", "one", "ones", "only", "onto", "or", "our", "ours", "ourselves", "out", "over", "overall", "per", "quite", "really", "she", "since", "so", "some", "somebody", "somehow", "someone", "something", "sometime", "sometimes", "somewhat", "somewhere", "soon", "such", "than", "that", "the", "then", "thence", "there", "thereafter", "thereby", "therefore", "therein", "thereupon", "these", "this", "those", "though", "through", "throughout", "thus", "to", "together", "too", "truly", "up", "upon", "was", "were", "what", "whatever", "when", "whence", "whenever", "where", "whereafter", "whereas", "whereby", "wherein", "whereupon", "wherever", "whether", "which", "while", "whither", "will", "yet", "you", "your", ",", ".", "`", "\'"
	};
//	protected double m_metricThreshold;
	
//	protected XmlRpcMetricClient m_metricClient;

	static Logger	logger	= Logger.getLogger(LlmEntailmentClassifier.class);

	protected boolean m_DEBUG = true;
	
/**
 * instantiates LlmComparator using named configuration file, which should be on
 *   the classpath
 *   
 * @param configFile_
 * @throws IOException 
 */
	public LlmComparator() throws IOException
	{
		m_stopwords = new HashSet< String >();

//		if ( !configFile_.equals( "" ) ) 
//			m_propertiesFile = configFile_;

		configure();
		
//		try
//		{
//			loadStopwords();			
//		}
//		catch( IOException e ) {
//			throw e;
//		}
//		m_metricClient = 
//			new XmlRpcMetricClient( m_metricName, m_metricHost, m_metricPort );
	}	
	
	/**
	 * Instantiates LlmComparator with default configuration file
	 * 
	 * @throws IOException
	 */
//	
//	public LlmComparator() throws IOException
//	{	
//		m_stopwords = new HashSet< String >();
//		configure(); // uses default config file
//		
//		try
//		{
//			loadStopwords();			
//		}
//		catch( IOException e ) {
//			throw e;
//		}
//		m_metricClient = 
//			new XmlRpcMetricClient( m_metricName, m_metricHost, m_metricPort );
//
//	}
	
/**
 * reads parameters from configuration file named by m_propertiesFile
 * 
 * @throws IllegalArgumentException
 * @throws IOException
 */
	
	/**
	 * default = true;
	 */
	public void useLemmas(boolean b) {
		m_useLemmas = b;
	}
	
	/**
	 * default = true;
	 */
	public void useMetric(boolean b) {
		m_useMetric = b;
	}
	
	/**
	 * default stop words =  DEFAULT_STOPWORDS
	 */
	public void setStopWords(String[] words) {
		m_stopwords = new HashSet< String >();
		for (int i = 0; i < words.length; i++) {
			m_stopwords.add(words[i]);			
		}		
	}
	
	/**
	 * default = true
	 * @param b
	 */
	public void computeSimpleScore(boolean b) {
		m_computeSimpleScore = b;
	}

	/**
	 * default 0.0001
	 * @param d
	 */
	public void setSimpleScoreThreshold(double d) {
		m_simpleScoreThreshold = d;
	}

	/**
	 * WNSim or NESim
	 * default is WNSim;
	 */
	public void setMetricName(String s) {
		m_metricName = s;
	}

	protected void configure() throws IllegalArgumentException, IOException 
	{
		m_DEBUG = false;
		m_useStopwords = true;
		m_useLemmas = true;
		m_useMetric = true;
		setStopWords(this.DEFAULT_STOPWORDS);
		m_computeSimpleScore = true;
		m_metricName = "WNSim";
		m_metricHost = "localhost";
		m_metricPort = 29022;
		m_simpleScoreThreshold = 0.0001;

//		BasicConfigurator.configure();
//		if ( m_DEBUG )
//		    logger.setLevel( Level.DEBUG );
//
//		logger.debug( "m_useStopwords: " + ( m_useStopwords ? "TRUE" : "FALSE" ) );
//		logger.debug( "m_useLemmas: " + ( m_useLemmas ? "TRUE" : "FALSE " ) );
//		logger.debug( "m_useMetric: " + ( m_useMetric ? "TRUE" : "FALSE" ) );
//		logger.debug( "m_computeSimpleScore: " + ( m_computeSimpleScore ? "TRUE" : "FALSE" ) );
//        logger.debug( "m_simpleScoreThreshold: " + m_simpleScoreThreshold );
//		logger.debug( "m_metricName: " + m_metricName );
//		logger.debug( "m_metricHost: " + m_metricHost );
//		logger.debug( "m_metricPort: " + m_metricPort );
	
	}

	
	public double compareTextAnnotations( TextAnnotation source_, TextAnnotation target_ )
	{		
		String[] sourceTokens = source_.getTokens();
		String[] targetTokens = target_.getTokens();
		
		return compareStringArrays( sourceTokens, targetTokens );
	}
	
	
	/**
	 * expects two strings representing text spans to be compared. 
	 * Tokenizes using the LBJTokenizer. 
	 * Normalizes the similarity score using the number of 
	 *   content words in target_.
	 *   
	 * @param source_
	 * @param target_
	 * @return similarity score (double) between 0 and 1.
	 */
	
	public double compareStrings( String source_, String target_ )
	{
		double llmScore = 0.0;
		
		String[] sourceTokens = getTokens( source_ );
		String[] targetTokens = getTokens( target_ );
		
		llmScore = compareStringArrays( sourceTokens, targetTokens );
		
		return llmScore;
	}
	
	
	private String[] getTokens( String source_ )
    {
	    LBJTokenizer tokenizer = new LBJTokenizer();
	    Pair< String[], IntPair[] > lbjTokens = tokenizer.tokenize( source_ );  
	    
	    return lbjTokens.getFirst();
    }

    /**
	 * compute the LLM score for a pair of string arrays. 
	 * if so configured, will ignore non-content words as specified in 
	 *    a stopwords file. 
	 *    
	 * @param sourceTokens_
	 * @param targetTokens_
	 * @return
	 */
	public double compareStringArrays( String[] sourceTokens_, String[] targetTokens_ )
	{
		Double llmScore = 0.0;
		
		String[] sourceToks = sourceTokens_;
		String[] targetToks = targetTokens_;	

		if ( m_useStopwords ) {
			sourceToks = removeStopwords( sourceTokens_ );
			targetToks = removeStopwords( targetTokens_ );
		}
		
		double totalScore = 0.0;
		
		for ( int i = 0; i < targetToks.length; ++i ) 
		{			
			ArgMax< Double, String > bestTarget = 
				new ArgMax< Double, String >( Double.NEGATIVE_INFINITY, "" );

			for ( int j = 0; j < sourceToks.length; ++j ) 
			{
				Double tokScore = compareTokens( sourceToks[ j ], targetToks[ i ] );
				
				bestTarget.update( tokScore, sourceToks[ j ] );
			}	
			
			totalScore += bestTarget.getMaxValue();
		}
		
		llmScore = totalScore / new Integer( targetToks.length ).doubleValue();
		
		return llmScore;
	}
	
	
	
	/**
	 * method for comparing pairs of tokens.  If configured to do so, will use
	 *   a word-level similarity metric, and may also coerce the resulting score 
	 *   to either zero or one. 
	 *   
	 * @param source_
	 * @param target_
	 * @return
	 */
	
	public Double compareTokens( String source_, String target_ ) {
		Double simScore = 0.0;
		
		if ( source_.equalsIgnoreCase( target_ ) )
			simScore = 1.0;
		else {
//			if ( m_useMetric ) {
//				MetricResponse response = m_metricClient.compareStrings( source_, target_ );
//			
//				if ( m_computeSimpleScore ) {
//					if ( response.score > m_simpleScoreThreshold ) {
//						simScore = 1.0;
//					}
//				}
//				else 
//					simScore = response.score;
//			}
		}
		
		return simScore;
	}
	
	/**
	 * filter non-content words from an input array of string
	 * 
	 * @param tokens_
	 * @return an array of string containing only non-stopwords
	 */
	
	public String[] removeStopwords( String[] tokens_ )
	{	
		List< String > filteredTokens = new ArrayList< String > ();
		
		for ( String tok : tokens_ )
		{
			String lcTok = tok.toLowerCase();
			
			if ( !m_stopwords.contains( lcTok ) ) 
				filteredTokens.add( tok );
		}
		
		if ( m_DEBUG ) {
			logger.debug( "## original tokens: " );
			logger.debug( StringUtil.arrayToString( tokens_ ) );
			logger.debug( "## filtered tokens: " );
			logger.debug( StringUtil.listToString( filteredTokens ) );
		}
		
		return filteredTokens.toArray( new String[0] );
	}

	

	
//
//	protected void loadStopwords() throws IOException 
//	{
//		BufferedReader reader = null;
//		
//		try { 
//			
//			InputStream stopwordStrm =  ClassLoader.getSystemResourceAsStream( m_stopwordFile );
//
//			if ( null == stopwordStrm )
//			{
//				logger.warn( "couldn't find file '" + m_stopwordFile + "' on classpath..." );
//				stopwordStrm = new FileInputStream( m_stopwordFile );
//			}
//			
//			{
//				String line;
//				reader = new BufferedReader( new InputStreamReader( stopwordStrm ) );
//
//				while ( (line = reader.readLine()) != null ) 
//					m_stopwords.add( line.toLowerCase() );
//
//			}
//		}
//		catch( IllegalArgumentException e ) {
//			throw e;
//		}
//		catch( IOException e ) {
//			throw e;
//		}
//		finally
//		{
//			if ( null != reader )
//				reader.close();
//		}
//		return;		
//	}
//	
}
